İmkanın sınırını zorlamadan imkansızı bilemezsin...
Herkese Merhaba
Cannon Bomb 3D isimli oyunu yaparken Trajectory olayını incelemiştim.Oyunda topu fırlatarak hedefleri vurmaya çalışıyorduk.
Bununla uğraşırken aklıma gelen fikir ise bunu bilgisayarın kendisi yapması oldu.
Yani bir havan topu sistemi yapmak istedim. Hedefin konumunu bildiğimiz sistem, hedefi kendiliğinden vuracak olan
fizik hesaplamasını yapacak.
Bu konuyu bir sürü alanda özellikle fizik hesaplamalı atış oyunlarında kullanabilirsiniz.
Hemen Başlayalım
Öncelikle bize zemin olarak bir düzlem gerekli bunun için "Plane" objesi oluşturup ismini "ground" yaptım, ayrıca x ve z scale degerlerini "5" yaptım ki biraz büyük olsun.
Ardından fırlatmak için bir top vb. obje gerek "sphere"objesi işimizi görür.
Daha sonra "target" isminde "Cube" objesinden farklı konumlarda hedefler yaptım ve "material" atadım.
-> Yaptığım tüm hazırlıklar resimde görüldüğü gibi.
Artık kodlamaya gecebiliriz ama önümüzde bir fizik problemi var. Problem ise konumunu bildiğimiz hedefleri tam isabetle vurmak.
Biraz düşündükten sonra lise de fizik dersinde öğrendiğimiz atışlar konusunun bize uygun olacağını anladım.
Bizim işimize yarayacak olan yerden yukarı atış ve yatay atışın birleşimi olacak.
Yatay Atışta formül belli
x = Vx * t
Birde Aşağıdan yukarıya atış var.
y = h + (Vy * t) - 1/2 * g * (t*t)
Tabi biz zamanı kullanamayız, hangi güçle fırlatacağımızıda bilmediğimiz için 2 bilinmeyenli bir denklem çözmek zorunda kalıyoruz.
Fırlatacağımız açıyı ve hedefe olan uzaklıgımızı bildiğimize göre yatay atış formülü üzerinden gidelim.
Öncelikle "t" yi bulmamız gerekiyor.
x = Vx * t
O halde "t"
t = x / Vx
Ve trigonometri sayesinde biliyoruz ki
Vx = V * cos(a) , Vy = V * sin(a)
Artık Aşağıdan yukarıya atışda t yerine çıkan sonucu koyarsak.
y = h + V * sin(a) * (x / V*cos(a)) - 1/2 * g * ((x * x) / (V * V) * (cos(a) * cos(a) ))
Burada formül t zaman sonra yüksekliği verdiğine göre biz bu zamanı topun yere dustugu an olarak alabiliriz yani "y = 0" olur.
Buradan da islemleri devam ettirirsek.
h + tan(a) * x = 1/2 * g * (x * x) / (V * V * cos(a) * cos(a))
Buradan da artık "V" yi bulabiliriz. Yani fırlatma gücünü
V = Karekök[(g*(x * x) / 2*(cos(a)*cos(a) * (h + tan(a) * x))]
Artık kodlamaya geçebiliriz. :D
-> Fırlatma açısına müdahale edebilmek için bir değişkene atıyorum.
public float launch_Angle;
-> Yerçekimi bu formülde çokca kullandığımız bir degişken onuda "private" bir degiskene atıyorum.
private float gravity;
-> Bütün olayların gerçekleşebilmesi için tabiki de nesnenin bir "rigidbody" componentinin olması gerkiyor.
private Rigidbody rb;
-> Start methodu içersinde bunları tanımlıyorum.
private void Start()
{
gravity = Physics.gravity.magnitude;
rb = gameObject.GetComponent<Rigidbody>();
}
-> Ardından hangi hedefe fırlatılacağını secmek için "Button" kullandım o yüzden "BtnClick" isimli bir method olusturup buton tıklama eventi haline getirdim.
public void BtnClick(Transform transform)
{
Launch(transform, launch_Angle);
}
-> Buton click eventinden hedefe fırlatmak için ise "Launch" methodunu çağırıyorum. Zaten hesaplamaları yapacagımız
method burası.
-> Hedefe topu fırlatırken hedef yükseliği ile topun baslangıç yüksekliği aynı olmayabilir bu yüzden formüldeki "h" degerini bulmak için "initHeight" isimli degiskene yükseklik farklarını atıyorum.
float initHeight = transform.position.y - target.position.y;
-> Sıra geldi hedefe olan mesafeyi bulmaya burada ise ilkokulda öğrendiğimiz "pisagor" bağıntısından yararlanıyoruz.
-> "horizontal distance" isimli degiskene mesafe bilgimizi atadık.
float distance_X = transform.position.x - target.position.x;
float distance_Z = transform.position.z - target.position.z;
float horizontal_distance = Mathf.Sqrt(Mathf.Pow(distance_X, 2) + Mathf.Pow(distance_Z, 2));
-> Artık gerekli olan degiskenlere sahip oldugumuz için formülü çözümlemeye baslayabiliriz.
-> Formül uzun oldugu ve bunu tek satırda yazması zor olacağı icin parcalara ayırdım.
-> Formüldeki pay kısmını first_process degiskenine atadım.
-> Payda kısmını ise second_process degiskenine atadım.
float first_Process = gravity * Mathf.Pow(horizontal_distance, 2);
float second_Process = 2 * Mathf.Pow(Mathf.Cos(initAngle), 2) * (initHeight + Mathf.Tan(initAngle) * horizontal_distance);
-> Artık bunların bölümünün karekökünü alabiliriz.
-> "Mathf.Sqrt" ile kareköklerini almamız gerekiyor fakat matematikden bildiğimiz üzere karekökün içerisi negatif olamaz fakat burada gravity yani yerçekimi (-9.81f) oldugu için negatif çıkıyor ve bunu engellememiz için karekök almadan önce "Mathf.Abs" ile çıkan sonucu pozitife çevirmemiz gerekiyor.
float velocity = Mathf.Sqrt(Mathf.Abs(first_Process / second_Process));
-> Bunu da yaptıkdan sonra velocity degiskenine gerekli olan fırlatma
gücünü atamış olduk.
-> Artık rb.velocity ile fırlatma işlemini yapabiliriz.
-> Tabi bunu Vector3 degerine gore yapmamız gerekiyor yani "3 boyutlu" düzleme aktarıcaz bunun icin vel_Z ve vel_Y isimli degiskenler olusturup taa başta belirttiğimiz üzere "Vx = V * cos(a)" ve "Vy = V * sin(a)" dan yani ileri ve yukarı
yönde uygulanacak kuvveti hesaplayarak degiskenlere atıyoruz.
float vel_Z = velocity * Mathf.Cos(initAngle);
float vel_Y = velocity * Mathf.Sin(initAngle);
rb.velocity = new Vector3(0.0f,vel_Y,vel_Z)
-> Fakat o zaman sadece z ekseni yani ileri yönlü fırlatma yapmıs olurduk fakat hedeflerimizin hepsi önümüzde değil arkamızda ve yan tarafımızda olan hedeflerden var.
-> Bu yüzden x kordinatı yan yan taraflarada kuvvet uygulanması gerekirki her yöne fırlatma yapabilelim. Burada açıklama biraz karışmış olabilir fakat daha sade nasıl anlatılır bilemiyorum.
-> Artık her yere atış işlemi gerçekleştirebiliriz
rb.velocity = new Vector3(vel_Z,vel_Y,vel_Z)
-> Fakat bir sorun daha var ve bu artık son sorun.
-> x ve z eksenine aynı kuvveti ugulanıyor bu da sadece 45 derecelik bir yatay fırlatma açısı olmasını sağlar oysaki bizim hedefimiz daha çok ve ya az açıda olabilir.
-> Bunun için açıya göre o eksendeki kuvvetlerin fazla veya eksik olması gerekir.
-> Bu sorunu ise Trigonometri deki birim çemberden çözebiliriz.
-> Birim cemberlerde bir eksenin uzunlugu 1 olarak alındıgına gore bizimde 1 almamız gerekir bunun içinde "normalized" yapmamız gerekir.
-> "unitcirle" adında bir degisken olusturup birim cemberi datasını atıyoruz
Vector2 unitCircle = new Vector2(distance_X, distance_Z).normalized;
-> artık her şeyi hesaplamış olduk tek yapmamız gereken fırlatma işlemini yapmak.
Ve bunun için
rb.velocity = new Vector3(vel_Z * unitCircle.x * -1.0f, vel_Y, vel_Z * unitCircle.y * -1.0f)
-> Ve Sonuc.
-> Denklemin tamamı
-> Sonraki gönderide görüşmek üzere.